
#include "session.h"

#ifndef CONFIG_MEDIA_EMULATE_ON_PC
#include <asm/shdev.h>
#endif
#include <asm/delay.h>

#include <uapi/stdlib.h>
#include <uapi/string.h>

#ifdef CONFIG_MEDIA_EMULATE_ON_PC
#include "ff/ff.h"
#else
#include "ff.h"

#if !defined(CONFIG_HAVE_DATBUFS) || defined(CONFIG_NO_DATBUFS)
#error configuration CONFIG_HAVE_DATBUFS must be defined, configuration CONFIG_NO_DATBUFS must be deleted !
#endif
#endif

///////////////////////////////////////////////////////////////////////////////////////////////
#define _RIFF		         (0x46464952)
#define _WAVE		         (0x45564157)
#define _FMT		         (0x20746D66)
#define _DATA		         (0x61746164)
#define _FACT		         (0x74636166)

typedef struct _GUID {
	uint32_t Data1;
	uint16_t Data2;
	uint16_t Data3;
	uint8_t  Data4[8];
} GUID;

typedef struct {
	uint16_t wFormatTag;
	uint16_t nChannels;
	uint32_t nSamplesPerSec;
	uint32_t nAvgBytesPerSec;
	uint16_t nBlockAlign;
	uint16_t wBitsPerSample;

	uint16_t cbSize;
	uint16_t wValidBitsPerSample;
	uint32_t dwChannelMask;
	GUID  SubFormat;

	long nBytesPerSample;
	long audioDataLen;
} WAVEFORMATEXTENSIBLE;

#define WAVE_FORMAT_PCM         0x0001
#define WAVE_FORMAT_EXTENSIBLE  0xFFFE /* Microsoft */

static uint16_t flget_word(const uint8_t *pbuf)
{
	uint16_t temp = pbuf[0];
	temp |= ((uint16_t)pbuf[1]) << 8;
	return temp;
}

static uint32_t flget_dword(const uint8_t *pbuf)
{
	uint32_t temp = pbuf[0];
	temp |= ((uint32_t)pbuf[1]) << 8;
	temp |= ((uint32_t)pbuf[2]) << 16;
	temp |= ((uint32_t)pbuf[3]) << 24;
	return temp;
}

static long find_wav_header(WAVEFORMATEXTENSIBLE *pWavHdr, uint8_t *pbuf, long size)
{
	uint8_t *pbc = pbuf, *pbd;
	uint32_t temp;

	// 'RIFF' : 52 49 46 46
	while (size >= 4) {
		temp = flget_dword(pbc);
		if (_RIFF == temp)
			break;
		size--;
		pbc++;
	}
	if (_RIFF != temp)
		return -EAGAIN;
	pbc += 8;

	// 'WAVE' : 57 41 56 45
	temp = flget_dword(pbc);
	if (_WAVE != temp)
		return -EAGAIN;
	pbc += 4;

	// 'fmt ' : 66 6D 74 20
	temp = flget_dword(pbc);
	if (_FMT != temp)
		return -EAGAIN;
	pbc += 4;

	// sizeof(PCMWAVEFORMAT)
	temp = flget_dword(pbc);
	pbc += 4;

	pbd = pbc + temp;

	// 'fact' : 66 61 63 74
	temp = flget_dword(pbd);
	if (temp == _FACT) {
		pbd += 4;
		temp = flget_dword(pbd); // fact size
		pbd += 4;
		pWavHdr->nBytesPerSample = flget_dword(pbd);
		pbd += temp;
		temp = flget_dword(pbd);
	}

	// 'data' : 64 61 74 61
	if (temp != _DATA)
		return -EAGAIN;
	pbd += 4;

	// PCM WAVE format header
	pWavHdr->wFormatTag = flget_word(pbc);
	pbc += 2;

	pWavHdr->nChannels = flget_word(pbc);
	pbc += 2;

	pWavHdr->nSamplesPerSec = flget_dword(pbc);
	pbc += 4;

	pWavHdr->nAvgBytesPerSec = flget_dword(pbc);
	pbc += 4;

	pWavHdr->nBlockAlign = flget_word(pbc);
	pbc += 2;

	pWavHdr->wBitsPerSample = flget_word(pbc);
	pbc += 2;

	if (pWavHdr->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
	{
		pWavHdr->cbSize = flget_word(pbc);
		pbc += 2;
		pWavHdr->wValidBitsPerSample = flget_word(pbc);
		pbc += 2;
		pWavHdr->dwChannelMask = flget_dword(pbc);
		pbc += 4;
		pWavHdr->SubFormat.Data1 = flget_dword(pbc);
		pbc += 4;
		pWavHdr->SubFormat.Data2 = flget_word(pbc);
		pbc += 2;
		pWavHdr->SubFormat.Data3 = flget_word(pbc);
		pbc += 2;
		memcpy(pWavHdr->SubFormat.Data4, pbc, 8);
	}

	pWavHdr->audioDataLen = flget_dword(pbd);
	pbd += 4;

	return (long)(pbd - pbuf);
}

///////////////////////////////////////////////////////////////////////////////////////////////
// WAV
///////////////////////////////////////////////////////////////////////////////////////////////

typedef struct
{
	struct SESSION *session, *session_audio;

	long load_size, load_offset, load_szcount;
	int eof;

	psysbuf_t load_buf;
	WAVEFORMATEXTENSIBLE wavfi;

	FIL file;

}session_unit_t;

static long SESSIONAPI(StreamLoad)(session_unit_t *punit)
{
	long l2, ret = 0;
	psysbuf_t load_buf;

	if (punit->session_audio == NULL)
		return 0;

	load_buf = punit->load_buf;
	if (load_buf == NULL) {
		load_buf = sysbuf_alloc(SYSBUF_GROUP_DATBUFS);
		if (load_buf == NULL)
			return -1;
		punit->load_buf = load_buf;
	}

	if (punit->load_size > 0)
	{
		l2 = load_buf->maxsize / 12 * 12;
		if (l2 >= punit->load_size) {
			l2 = punit->load_size;
			load_buf->flags |= SYS_BUF_FLAG_EOF;
		}
#ifdef CONFIG_MEDIA_EMULATE_ON_PC
		f_read(&(punit->file), (void*)(load_buf->haddr), l2, NULL);
#else
		f_read(&(punit->file), (void*)HWADDR(load_buf->haddr), l2, NULL);
#endif
		load_buf->size = l2;
		punit->load_offset += l2;
		punit->load_size -= l2;

		l2 = punit->load_offset - punit->load_szcount;
		if ((l2 > punit->load_size / 100L) || (l2 > 0x40000)) {
			punit->load_szcount = punit->load_offset;
			debug("\t\tProcess : %f%%, 0x%lx (%ld) bytes\n",
				100.0f*(float)punit->load_offset/(float)(punit->load_offset+punit->load_size),
				punit->load_offset, punit->load_offset
				);
		}
	}
	else
	{
		psysbuf_t buf = SessionBufferPop(punit->session);
		if (buf) {
			if (buf->list.group == load_buf->list.group) {
				load_buf = buf;
			}
			else {
				phys_addr_t src, dst;
				src = buf->haddr + buf->offset;
				dst = load_buf->haddr + load_buf->offset;
#ifdef XA_USE_EDMA_IOBUF
				edma_copy(EDMA_CHN_IO, dst, src, buf->size);
#else
				memcpy((void*)dst, (void*)src, buf->size);
#endif
				load_buf->size = buf->size;
				if (buf->flags & SYS_BUF_FLAG_EOF)
					load_buf->flags |= SYS_BUF_FLAG_EOF;
				load_buf->user = buf->user;
				sysbuf_free(buf);
			}
		}
		else {
			load_buf = NULL;
		}
	}

	if (load_buf) {
		session_buffer_t *sbf;

		if (load_buf->flags & SYS_BUF_FLAG_EOF)
			punit->eof = 1;

		sbf = SessionBufferPush(punit->session_audio, load_buf);
		if (sbf) {
			if (punit->load_buf == load_buf)
				punit->load_buf = NULL;
			ret = load_buf->size;
		}
		else {
			if (punit->load_buf != load_buf)
				sysbuf_free(load_buf);
			ret = 0;
		}
	}

	return ret;
}

static
SESSIONSTATE SESSIONAPI(SessionRun)(struct SESSION *session)
{
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return SSTATE_NULL;

	if (session->state == SSTATE_RUNNING
		|| session->state == SSTATE_RUNNING_IDLE)
	{
		if (punit->session_audio != NULL)
		{
			/* try to fill stream buffer */
			if (session->state == SSTATE_RUNNING_IDLE) {
				SESSIONAPI(StreamLoad)(punit);
			}

			if (SessionBufferTopNum(punit->session_audio) > 1)
				session->state = SSTATE_RUNNING;
			else
				session->state = SSTATE_RUNNING_IDLE;

			SESSIONSTATE state = punit->session_audio->SessionRun(punit->session_audio);
			if (punit->eof && state == SSTATE_STOP) {
				session->state = SSTATE_STOP;
			}
		}
	}

	return session->state;
}

static
int SESSIONAPI(SSCMD_STREAM_START)(struct SESSION *session, session_file_t *file)
{
	int res;
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL || session->state < SSTATE_INITED)
		return -EPERM;

	/* Initial loading */
	punit->load_buf = NULL;
	punit->load_size = 0;
	punit->load_offset = 0;
	punit->load_szcount = 0;
	punit->eof = 0;

	punit->session_audio = SessionGet(SST_I2SOUT);

	if (file->filename) {
		long off;

		if (f_open(&(punit->file), file->filename, FA_READ) != FR_OK)
		{
			debug("open stream file error \n");
			punit->session_audio = NULL;
			return -EBADF;
		}
		else
		{
			punit->load_size = f_size(&(punit->file));
		}

		punit->load_buf = sysbuf_alloc(SYSBUF_GROUP_DATBUFS);
		if (punit->load_buf == NULL) {
			punit->session_audio = NULL;
			return -ENOMEM;
		}
#ifdef CONFIG_MEDIA_EMULATE_ON_PC
		f_read(&(punit->file), (void*)(punit->load_buf->haddr), 512, NULL);
#else
		f_read(&(punit->file), (void*)HWADDR(punit->load_buf->haddr), 512, NULL);
#endif
		off = find_wav_header(&punit->wavfi, (uint8_t *)punit->load_buf->haddr, 512);
		if (off <= 0) {
			punit->session_audio = NULL;
			if (punit->load_buf) {
				sysbuf_free(punit->load_buf);
				punit->load_buf = NULL;
			}
			f_close(&punit->file);
			return -EBADF;
		}

		if (punit->session_audio) {
			audio_info_t info;
			info.samplingsize = punit->wavfi.wBitsPerSample;
			info.samplingrate = punit->wavfi.nSamplesPerSec;
			SessionCommand(punit->session_audio, SSCMD_SET_AUDIO_INFO, &info);
		}

		punit->load_size = punit->wavfi.audioDataLen;
		f_lseek(&(punit->file), off);
	}

	if (punit->session_audio != NULL) {
		session_file_t fl;
		fl.type = file->type;
		fl.ftype = file->ftype;
		fl.tv = file->tv;
		fl.ta = file->ta;
#ifdef MEDIA_AV_SYNC
		fl.sync = file->sync;
		fl.sync->a_real_rate = file->ta.sample_rate;
#endif
		fl.filename = NULL;
		res = SessionCommand(punit->session_audio, SSCMD_STREAM_START, &fl);
		if (res) {
			punit->session_audio = NULL;
			if (punit->load_buf) {
				sysbuf_free(punit->load_buf);
				punit->load_buf = NULL;
			}
			f_close(&punit->file);
			return res;
		}
	}

	SessionBufferDeInit(session);

	session->state = SSTATE_RUNNING_IDLE;

	return 0;
}

static
int SESSIONAPI(SSCMD_STREAM_STOP)(struct SESSION *session)
{
	int res;
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL || session->state < SSTATE_RUNNING)
		return -EPERM;

	// set state
	session->state = SSTATE_STOP;

	if (punit->session_audio != NULL) {
		res = SessionCommand(punit->session_audio, SSCMD_STREAM_STOP, NULL);
		if (res) {
		}
		punit->session_audio = NULL;
	}

	if (punit->load_buf) {
		sysbuf_free(punit->load_buf);
		punit->load_buf = NULL;
	}

	f_close(&punit->file);

	SessionBufferDeInit(session);

	// set state
	session->state = SSTATE_INITED;

	return 0;
}

static
int SESSIONAPI(SessionCommand)(struct SESSION *session, int cmd, void *params)
{
	int ret = 0;
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return -EINVAL;

	switch (cmd)
	{
	case SSCMD_STREAM_START:
	{
		debug("Session '%s' command 'SSCMD_STREAM_START'\n", session->name);
		return SESSIONAPI(SSCMD_STREAM_START)(session, (session_file_t *)params);
	}break;
	case SSCMD_STREAM_PAUSE:
	{
		debug("Session '%s' command  'SSCMD_STREAM_PAUSE'\n", session->name);
		if (session->state == SSTATE_RUNNING
			|| session->state == SSTATE_RUNNING_IDLE)
			session->state = SSTATE_RUNNING_PAUSE;
	}break;
	case SSCMD_STREAM_RESUME:
	{
		debug("Session '%s' command  'SSCMD_STREAM_RESUME'\n", session->name);
		if (session->state == SSTATE_RUNNING_PAUSE)
			session->state = SSTATE_RUNNING;
	}break;
	case SSCMD_STREAM_STOP:
	{
		debug("Session '%s' command  'SSCMD_STREAM_STOP'\n", session->name);
		return SESSIONAPI(SSCMD_STREAM_STOP)(session);
	}break;
	case SSCMD_STREAM_BUFCHANGED:
	{
	}break;
	default:
	{
		debug("Session '%s' command %d unknown !\n", session->name, cmd);
		ret = -EINVAL;
	};
	};

	return ret;
}

static
void SESSIONAPI(SessionDeInit)(struct SESSION *session)
{
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	else
		return;

	debug("SessionDeInit : %s ..\n", session->name);

	SessionBufferDeInit(session);

	if (punit != NULL) {
		free(punit);
	}

	session->handle = NULL;
}

static
SESSIONHANDLE SESSIONAPI(SessionInit)(struct SESSION *session)
{
	session_unit_t *punit = (session_unit_t *)malloc(sizeof(session_unit_t) + 64);
	if (punit == NULL)
		return NULL;

	debug("SessionInit : %s ..\n", session->name);

	memset(punit, 0, sizeof(session_unit_t));
	punit->session = session;
	session->SessionRun = &SESSIONAPI(SessionRun);
	session->SessionCommand = &SESSIONAPI(SessionCommand);
	session->SessionDeInit = &SESSIONAPI(SessionDeInit);
	session->state = SSTATE_INITED;

	SessionBufferInit(session);

	session->handle = (SESSIONHANDLE)punit;
	return session->handle;
}

struct SESSION gSession_wav =
{ "wav", (1 << SST_WAV),
&SESSIONAPI(SessionInit),
};
